package com.zshan.clinic.common.util.http;


import org.apache.http.Consts;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.*;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.ByteArrayBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultConnectionKeepAliveStrategy;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;

import javax.net.ssl.SSLContext;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.SocketTimeoutException;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * HttpClientHttpUtil - 基于 Apache HttpClient 4.5.x 的 HTTP 工具类
 * 线程安全：内部使用连接池的 CloseableHttpClient 单例。
 * 注意：调用方无需关闭客户端；downloadTo 等方法会正确关闭响应实体。
 */
public class HttpUtil {

    /* =================== 测试入口 =================== */
    public static void main(String[] args) {
        try {
            // 1) GET 示例
            Resp r1 = HttpUtil.get("https://httpbin.org/get",
                    Collections.singletonMap("q", "hello"),
                    Collections.singletonMap("X-Test", "123"));
            System.out.println("GET: " + r1);

            // 2) POST JSON 示例
            String json = "{\"name\":\"Tom\",\"age\":18}";
            Resp r2 = HttpUtil.postJson("https://httpbin.org/post", json, null);
            System.out.println("POST JSON: " + r2);

            // 3) POST 表单示例
            Map<String, String> form = new HashMap<String, String>();
            form.put("username", "tom");
            form.put("password", "123456");
            Resp r3 = HttpUtil.postForm("https://httpbin.org/post", form, null);
            System.out.println("POST Form: " + r3);

            // 4) 文件上传示例
            byte[] fileBytes = "hello file".getBytes("UTF-8");
            Map<String, String> extra = new HashMap<String, String>();
            extra.put("bizId", "42");
            Resp r4 = HttpUtil.upload("https://httpbin.org/post",
                    "file",
                    "hello.txt",
                    fileBytes,
                    extra,
                    null,
                    ContentType.DEFAULT_BINARY);
            System.out.println("Upload: " + r4);

            // 5) 文件下载示例
            Path target = new java.io.File("download.json").toPath();
            HttpUtil.downloadTo("https://httpbin.org/json", null, null, target);
            System.out.println("Downloaded file saved to: " + target.toAbsolutePath());

        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /* =================== 默认配置（可按需调整） =================== */

    /** 连接超时（毫秒） */
    private static final int CONNECT_TIMEOUT_MS = 10_000;
    /** 读超时（毫秒） */
    private static final int SOCKET_TIMEOUT_MS = 30_000;
    /** 从连接池获取连接超时（毫秒） */
    private static final int CONNECTION_REQUEST_TIMEOUT_MS = 5_000;
    /** 最大连接数 */
    private static final int MAX_TOTAL = 200;
    /** 每路由最大连接数 */
    private static final int MAX_PER_ROUTE = 50;
    /** 重试次数，仅幂等请求可重试 */
    private static final int RETRY_COUNT = 2;

    /** 默认请求配置 */
    private static final RequestConfig DEFAULT_REQUEST_CONFIG = RequestConfig.custom()
            .setConnectTimeout(CONNECT_TIMEOUT_MS)
            .setSocketTimeout(SOCKET_TIMEOUT_MS)
            .setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT_MS)
            .build();

    /** 单例客户端（连接池 + KeepAlive + 幂等重试） */
    private static volatile CloseableHttpClient CLIENT = createPooledClient(null, null);

    private HttpUtil() {

    }

    /* =================== 客户端构建 & 覆盖 =================== */

    /**
     * 获取全局单例客户端。
     */
    public static CloseableHttpClient getClient() {
        return CLIENT;
    }

    /**
     * 覆盖默认客户端（例如注入代理或自定义重试策略）。
     */
    public static void setClient(CloseableHttpClient client) {
        if (client != null) {
            CLIENT = client;
        }
    }

    /**
     * 创建一个带连接池的客户端。
     *
     * @param proxy 可选 HTTP 代理（为 null 时不开启）
     * @param defaultHeaders 可选默认请求头
     */
    public static CloseableHttpClient createPooledClient(HttpHost proxy, List<Header> defaultHeaders) {
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
        cm.setMaxTotal(MAX_TOTAL);
        cm.setDefaultMaxPerRoute(MAX_PER_ROUTE);

        CloseableHttpClient httpClient = HttpClients.custom()
                .setConnectionManager(cm)
                .setDefaultRequestConfig(DEFAULT_REQUEST_CONFIG)
                .setDefaultHeaders(defaultHeaders != null ? defaultHeaders : Collections.<Header>emptyList())
                .setKeepAliveStrategy(DefaultConnectionKeepAliveStrategy.INSTANCE)
                // 仅对 GET/HEAD/DELETE/OPTIONS/TRACE 等幂等方法重试；POST 默认不重试
                .setRetryHandler(new DefaultHttpRequestRetryHandler(RETRY_COUNT, false))
                .setProxy(proxy)
                .evictExpiredConnections()
                .evictIdleConnections(30, TimeUnit.SECONDS)
                .build();

        return httpClient;
    }

    /**
     * 创建“信任所有证书”的不安全客户端（仅调试/内网使用！）
     */
    public static CloseableHttpClient createInsecureSslClient() {
        try {
            SSLContext sslContext = SSLContexts.custom()
                    .loadTrustMaterial(null, (X509Certificate[] chain, String authType) -> true)
                    .build();

            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                    sslContext,
                    new String[]{"TLSv1.2", "TLSv1.3"},
                    null,
                    NoopHostnameVerifier.INSTANCE
            );

            Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                    .register("http", PlainConnectionSocketFactory.getSocketFactory())
                    .register("https", sslsf)
                    .build();

            PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
            cm.setMaxTotal(MAX_TOTAL);
            cm.setDefaultMaxPerRoute(MAX_PER_ROUTE);

            CloseableHttpClient httpClient = HttpClients.custom()
                    .setConnectionManager(cm)
                    .setDefaultRequestConfig(DEFAULT_REQUEST_CONFIG)
                    .setKeepAliveStrategy(DefaultConnectionKeepAliveStrategy.INSTANCE)
                    .setRetryHandler(new DefaultHttpRequestRetryHandler(RETRY_COUNT, false))
                    .build();

            return httpClient;
        } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
            throw new IllegalStateException("Create insecure SSL client failed", e);
        }
    }

    /* =================== 便捷方法：GET / DELETE / POST / PUT =================== */
    public static Resp get(String url) throws IOException {
        HttpGet req = new HttpGet(url);
        applyHeaders(req, null);
        return execute(req);
    }

    public static Resp get(String url, Map<String, String> query, Map<String, String> headers) throws IOException {
        String fullUrl = appendQuery(url, query);
        HttpGet req = new HttpGet(fullUrl);
        applyHeaders(req, headers);
        return execute(req);
    }

    public static Resp delete(String url, Map<String, String> query, Map<String, String> headers) throws IOException {
        String fullUrl = appendQuery(url, query);
        HttpDelete req = new HttpDelete(fullUrl);
        applyHeaders(req, headers);
        return execute(req);
    }

    /** POST JSON */
    public static Resp postJson(String url, String json, Map<String, String> headers) throws IOException {
        HttpPost req = new HttpPost(url);
        applyHeaders(req, headers);
        if (json == null) {
            json = "";
        }
        req.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON));
        return execute(req);
    }

    /** PUT JSON */
    public static Resp putJson(String url, String json, Map<String, String> headers) throws IOException {
        HttpPut req = new HttpPut(url);
        applyHeaders(req, headers);
        if (json == null) {
            json = "";
        }
        req.setEntity(new StringEntity(json, ContentType.APPLICATION_JSON));
        return execute(req);
    }

    /** POST - application/x-www-form-urlencoded */
    public static Resp postForm(String url, Map<String, String> form, Map<String, String> headers) throws IOException {
        HttpPost req = new HttpPost(url);
        applyHeaders(req, headers);
        String body = toQueryString(form);
        req.setEntity(new StringEntity(body, ContentType.APPLICATION_FORM_URLENCODED.withCharset(Consts.UTF_8)));
        return execute(req);
    }

    /* =================== 文件上传 / 下载 =================== */

    /**
     * Multipart 表单上传（支持额外表单字段）
     */
    public static Resp upload(String url,
                              String fileField,
                              String filename,
                              byte[] fileBytes,
                              Map<String, String> extraFields,
                              Map<String, String> headers,
                              ContentType fileContentType) throws IOException {
        if (fileContentType == null) {
            fileContentType = ContentType.DEFAULT_BINARY;
        }
        HttpPost req = new HttpPost(url);
        applyHeaders(req, headers);

        MultipartEntityBuilder mb = MultipartEntityBuilder.create();
        if (extraFields != null) {
            for (Map.Entry<String, String> e : extraFields.entrySet()) {
                String k = e.getKey();
                String v = e.getValue() != null ? e.getValue() : "";
                mb.addTextBody(k, v, ContentType.TEXT_PLAIN.withCharset(Consts.UTF_8));
            }
        }
        ByteArrayBody fileBody = new ByteArrayBody(fileBytes != null ? fileBytes : new byte[0], fileContentType, filename);
        mb.addPart(fileField, fileBody);

        req.setEntity(mb.build());
        return execute(req);
    }

    /**
     * 下载到文件（自动建父目录；覆盖已存在）
     */
    public static void downloadTo(String url,
                                  Map<String, String> query,
                                  Map<String, String> headers,
                                  Path targetFile) throws IOException {
        String fullUrl = appendQuery(url, query);
        HttpGet req = new HttpGet(fullUrl);
        applyHeaders(req, headers);

        CloseableHttpResponse resp = null;
        try {
            resp = CLIENT.execute(req);
            int status = resp.getStatusLine().getStatusCode();
            if (status < 200 || status >= 300) {
                EntityUtils.consumeQuietly(resp.getEntity());
                throw new IOException("HTTP " + status + " when downloading: " + fullUrl);
            }
            HttpEntity entity = resp.getEntity();
            if (entity == null) {
                throw new IOException("Empty body when downloading: " + fullUrl);
            }
            if (targetFile.getParent() != null) {
                Files.createDirectories(targetFile.getParent());
            }
            try (InputStream in = entity.getContent(); FileOutputStream out = new FileOutputStream(targetFile.toFile())) {
                byte[] buf = new byte[8192];
                int len;
                while ((len = in.read(buf)) != -1) {
                    out.write(buf, 0, len);
                }
            }
        } finally {
            if (resp != null) {
                EntityUtils.consumeQuietly(resp.getEntity());
                try {
                    resp.close();
                } catch (IOException ignored) {
                }
            }
        }
    }

    /* =================== 底层执行 & 工具 =================== */

    /**
     * 执行请求并封装为 Resp。
     * 注意：响应体会被完整读入字符串（适合 JSON 文本）。大文件请走 downloadTo。
     */
    public static Resp execute(HttpUriRequest request) throws IOException {
        CloseableHttpResponse response = null;
        try {
            response = CLIENT.execute(request, HttpClientContext.create());
            int code = response.getStatusLine().getStatusCode();
            Map<String, List<String>> hdrs = new LinkedHashMap<String, List<String>>();
            for (Header h : response.getAllHeaders()) {
                String name = h.getName();
                String value = h.getValue();
                if (!hdrs.containsKey(name)) {
                    hdrs.put(name, new ArrayList<String>());
                }
                hdrs.get(name).add(value);
            }
            String body = null;
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                body = EntityUtils.toString(entity, Consts.UTF_8);
            }
            return new Resp(code, hdrs, body);
        } catch (ConnectTimeoutException | SocketTimeoutException e) {
            throw e;
        } catch (ClientProtocolException e) {
            throw e;
        } catch (IOException e) {
            throw e;
        } finally {
            if (response != null) {
                EntityUtils.consumeQuietly(response.getEntity());
                try {
                    response.close();
                } catch (IOException ignored) {
                }
            }
        }
    }

    private static void applyHeaders(HttpRequestBase req, Map<String, String> headers) {
        req.setConfig(DEFAULT_REQUEST_CONFIG);
        if (headers != null) {
            for (Map.Entry<String, String> e : headers.entrySet()) {
                if (e.getKey() != null && e.getValue() != null) {
                    req.addHeader(new BasicHeader(e.getKey(), e.getValue()));
                }
            }
        }
    }

    /** 在 URL 后拼接 query 参数（自动处理已有 ? 或 &） */
    public static String appendQuery(String url, Map<String, String> query) {
        if (query == null || query.isEmpty()) {
            return url;
        }
        StringBuilder sb = new StringBuilder(url);
        if (url.contains("?")) {
            sb.append("&");
        } else {
            sb.append("?");
        }
        sb.append(toQueryString(query));
        return sb.toString();
    }

    /** Map -> x-www-form-urlencoded */
    public static String toQueryString(Map<String, String> map) {
        if (map == null || map.isEmpty()) {
            return "";
        }
        StringBuilder sb = new StringBuilder();
        boolean first = true;
        for (Map.Entry<String, String> e : map.entrySet()) {
            if (!first) {
                sb.append("&");
            } else {
                first = false;
            }
            String k = e.getKey() != null ? e.getKey() : "";
            String v = e.getValue() != null ? e.getValue() : "";
            sb.append(urlEncode(k)).append("=").append(urlEncode(v));
        }
        return sb.toString();
    }

    private static String urlEncode(String s) {
        try {
            return URLEncoder.encode(s, Consts.UTF_8.name());
        } catch (Exception e) {
            return s;
        }
    }

    /* =================== 响应封装 =================== */

    public static class Resp {
        private final int status;
        private final Map<String, List<String>> headers;
        private final String body;

        public Resp(int status, Map<String, List<String>> headers, String body) {
            this.status = status;
            this.headers = headers;
            this.body = body;
        }

        public int getStatus() {
            return status;
        }

        public Map<String, List<String>> getHeaders() {
            return headers;
        }

        public String getBody() {
            return body;
        }

        public boolean is2xx() {
            return status >= 200 && status < 300;
        }

        public boolean isOk() {
            return status == 200;
        }

        public Optional<String> header(String name) {
            List<String> list = headers != null ? headers.get(name) : null;
            if (list == null || list.isEmpty()) {
                return Optional.empty();
            } else {
                return Optional.of(list.get(0));
            }
        }

        @Override
        public String toString() {
            String preview = body == null ? "null" : (body.length() > 512 ? body.substring(0, 512) + "..." : body);
            return "Resp{status=" + status + ", headers=" + headers + ", body=" + preview + "}";
        }
    }
}

